/**
 * 
 */
package com.pxingg.communication.adapter;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.pxingg.algorithm.ByteArrayQueue;
import com.pxingg.encrypt.crc.CRC;
import com.pxingg.exception.XException;
import com.pxingg.model.pubsub.Message;
import com.pxingg.utils.HexStringUtils;

/**
 * 实现从外部接收到的二进制消息到系统内部Message的相互转换<br>
 * 并把转换生产的内部消息发布到：messageName<br>
 * 》 这里需要转换的消息具有下面的格式<br>
 * 1. 特殊字符：<br>
 * a) 0xFA（消息内容中转义为：0xFF0A）<br>
 * b) 0xFB（消息内容中转义为：0xFF0B）表示消息的开始<br>
 * c) 0xFC（消息内容中转义为：0xFF0C）<br>
 * d) 0xFD（消息内容中转义为：0xFF0D）<br>
 * e) 0xFE（消息内容中转义为：0xFF0E） 表示消息的结束<br>
 * f) 0xFF（消息内容中转义为：0xFF0F） 在消息中做转义符号使用<br>
 * 
 * 消息中每个字段的含义<br>
 * <table>
 * <tr>
 * <th>编号</th>
 * <th>开始位置</th>
 * <th>长度</th>
 * <th>字段名</th>
 * <th>字段含义</th>
 * <th>备注</th>
 * </tr>
 * <tr>
 * <td>1</td>
 * <td>0</td>
 * <td>1</td>
 * <td>uint8_messageBeginMark</td>
 * <td>标记一条消息的开始</td>
 * <td>值恒为0xFB</td>
 * </tr>2
 * <tr>
 * <td>2-n</td>
 * <td>2</td>
 * <td>N-1</td>
 * <td>消息内容</td>
 * <td>消息内容</td>
 * <td>根据不同的设备不同而不同</td>
 * </tr>
 * <tr>
 * <td>n+1</td>
 * <td>n+1</td>
 * <td>1</td>
 * <td>uint8_checkSum</td>
 * <td>消息的校验和</td>
 * <td>使用CRC.iButtonCRC8()计算得到。</td>
 * </tr>
 * <tr>
 * <td>7</td>
 * <td>N+2</td>
 * <td>1</td>
 * <td>uint8_messageEndMark</td>
 * <td>标记一条消息的结尾</td>
 * <td>值恒为0xFE</td>
 * </tr>
 * </table>
 * 
 * 
 * @author pxingg
 * 
 */
public class FixedMarkerMessageAdapter
{
    /**
     * The log of this class
     */
    static Log log = LogFactory.getLog(FixedMarkerMessageAdapter.class);
    /**
     * 接收到的所有二进制消息的队列
     */
    private final ByteArrayQueue receiveQueueBuffer = new ByteArrayQueue();
    /**
     * 生产消息后，需要发布的消息名
     */
    String messageName;

    /**
     * 表示消息的开始
     */
    final private byte MESSAGE_START_FLAG = (byte) 0xFB;
    /**
     * 表示消息的结束
     */
    final private byte MESSAGE_END_FLAG = (byte) 0xFE;
    /**
     * 在消息中做转义符号使用,如0xFE消息内容中转义为：0xFF0E
     */
    final private byte MESSAGE_ESCAPE_FLAG = (byte) 0xFF;

    /**
     * 构造函数
     * 
     * @param messageName
     *            转换生产的内部消息发布的消息名
     */
    public FixedMarkerMessageAdapter(final String messageName)
    {
        this.messageName = messageName;
    }

    /**
     * 处理收到的消息码流，按照消息开始和结束标记分割消息，进行校验，并把消息发布到messageName中
     * 
     * @param data
     *            接收到的码流字节
     * @param length
     *            接收到的码流长度
     */
    public void receive(byte data[], final int length)
    {

        if (data == null || length <= 0)
        {
            FixedMarkerMessageAdapter.log.debug("receive data is null");
            return;
        }

        synchronized (receiveQueueBuffer)
        {
            receiveQueueBuffer.append(data, length);

            while (true)
            {
                data = receiveQueueBuffer.serveUntill(MESSAGE_END_FLAG);
                if (data == null)
                {
                    // 目前已经收到的队列为空，或者收到的消息还不足以形成一个新的消息
                    return;
                }

                if (data.length < 4)
                {
                    // 最短的消息都应该有四个字节
                    FixedMarkerMessageAdapter.log.error("get an invalid message whose length is less than 4, data is [" + HexStringUtils.hex2String(data, ", ") + "]");
                    continue;
                }

                // 统计开始标记的格式
                int startFlagNum = 0;
                int lastStartFlagIndex = 0;
                for (int i = 0; i < data.length; i++)
                {
                    if (data[i] == MESSAGE_START_FLAG)
                    {
                        lastStartFlagIndex = i;
                        startFlagNum++;
                    }
                }

                if (startFlagNum == 0)
                {
                    // 没有开始标记的消息是不合法的消息，需要丢弃
                    FixedMarkerMessageAdapter.log.error("message does not have start flag 0xFB, data is [" + HexStringUtils.hex2String(data, ", ") + "]");
                    continue;
                }

                if (startFlagNum > 1)
                {
                    // 如果有多个开始标记，就获取最后一个，构成一条新的消息
                    FixedMarkerMessageAdapter.log.error("message have " + startFlagNum + " start flag 0xFB, will only reserve the last one, data is [" + HexStringUtils.hex2String(data, ", ") + "]");

                    final byte newData[] = new byte[data.length - lastStartFlagIndex];
                    for (int i = 0; i < newData.length; i++)
                    {
                        newData[i] = data[lastStartFlagIndex + i];
                    }

                    if (newData.length < 4)
                    {
                        FixedMarkerMessageAdapter.log.error("get an reserve message whose length is less than 4, data is [" + HexStringUtils.hex2String(newData, ", ") + "]");
                        continue;
                    }

                    data = newData;
                }

                // 下面开始对转义字符进行处理

                // 标记上一个是不是转义字符
                boolean lastIsEscape = false;
                // 转义字符的个数
                int escapeNum = 0;
                for (int i = 0; i < data.length; i++)
                {
                    final byte b = data[i];

                    if (lastIsEscape)
                    {
                        if (b < 0x0A || b > 0x0F)
                        {
                            FixedMarkerMessageAdapter.log.error("message data after espace 0xFF is not in [0x0A, 0x0F], data is [" + HexStringUtils.hex2String(data, ", ") + "]");
                            continue;
                        }
                        lastIsEscape = false;
                    }

                    if (b == MESSAGE_ESCAPE_FLAG)
                    {
                        lastIsEscape = true;
                        escapeNum++;
                    }
                }

                // 对转义字符进行处理，去掉其中的转移字符
                if (escapeNum > 0)
                {
                    final byte newData[] = new byte[data.length - escapeNum];
                    escapeNum = 0;
                    lastIsEscape = false;
                    for (int i = 0; i < data.length; i++)
                    {
                        final byte b = data[i];

                        if (lastIsEscape)
                        {
                            // 上一个是转义字符，那么当前字符加上0XF0
                            newData[i - escapeNum] = (byte) (0x00F0 + b);
                            lastIsEscape = false;
                        }

                        if (b == MESSAGE_ESCAPE_FLAG)
                        {
                            // 转移字符直接做标记，然后略过
                            lastIsEscape = true;
                            escapeNum++;
                        }
                        else
                        {
                            newData[i - escapeNum] = b;
                        }
                    }

                    data = newData;
                }

                // 对数据做校验和的检查
                if (CRC.iButtonCRC8(data, data.length - 1) != data[data.length - 1])
                {
                    FixedMarkerMessageAdapter.log.error("the CRC of message is error, data is [" + HexStringUtils.hex2String(data, ", ") + "]");
                    continue;
                }

                // 到现在为止，所有的检查都已经结束，认为数据时合法的数据
                final Message message = new Message(messageName);
                message.setData("dataType", "data2hex");
                // 转换成数据格式时，需要去掉消息的头，尾和校验和
                message.setData("data", HexStringUtils.hex2String(data, "-", 1, data.length - 3));

                // 把消息发布出去
                message.publish();
            }
        }
    }

    /**
     * 把生产的二进制码流，增加上校验和，消息头尾，做转义处理，转换为内部消息格式的码流
     * 
     * @param message
     *            监听到的消息
     * @return 封装后的二进制码流<br>
     *         null: 获取到的数据不合法
     */
    public byte[] send(final Message message)
    {
        if (!"data2hex".equals(message.getData("dataType")))
        {
            FixedMarkerMessageAdapter.log.error("the message data type is not data2hex,\n" + message);
            message.setResult("result", "data type is not data2hex");
            return null;
        }

        try
        {
            final byte[] data = HexStringUtils.string2Hex(message.getData("data"), "-");

            final byte send[] = new byte[data.length * 2 + 4];
            int sendIndex = 0;
            byte b;
            send[sendIndex++] = MESSAGE_START_FLAG;
            for (int i = 0; i <= data.length; i++)
            {
                if (i != data.length)
                {
                    b = data[i];
                }
                else
                {
                    // 这个字节应该是生产的校验和
                    b = CRC.iButtonCRC8(data, data.length);

                }
                final int value = b & 0xFF;
                if (0xFA <= value && value <= 0xFF)
                {
                    send[sendIndex++] = MESSAGE_ESCAPE_FLAG;
                    send[sendIndex++] = (byte) (value & 0x0F);
                }
            }
            send[sendIndex++] = MESSAGE_END_FLAG;

            final byte result[] = new byte[sendIndex];
            for (int i = 0; i < sendIndex; i++)
            {
                result[i] = send[i];
            }

            message.setResult("result", "success");
            return result;
        }
        catch (final XException e)
        {
            FixedMarkerMessageAdapter.log.error("the message data is not invalid,\n" + message);
            FixedMarkerMessageAdapter.log.error(e);
            message.setResult("result", "data is not valid");
            return null;
        }

    }
}
